/**
 * 封装、解析企业微信 mqtt 消息内容
 */
import { v4 as uuidv4 } from 'uuid'

/**
 * ==============================
 * 类型定义
 * ==============================
 */

export type AnyObject = Record<string, unknown>

// Ext 为 never 时不合并，直接返回原值（与 never 合并的结果始终是 never）
type SafeCombine<Basic, Ext = never> = [Ext] extends [never] ? Basic : Basic & Ext

/* 消息种类 */
export enum MessageType {
  Manage = 'Manage', // 管理类消息（hook 上层收发的消息）
  Hook = 'Hook', // 没有 wxid 的 hook 消息
  HookPlain = 'HookPlain', // 有 wxid 的 hook 消息
}

/* 接收消息 */

// 原始消息
type RawRecvMessage<Ext extends AnyObject = never> = SafeCombine<
  {
    msgId: string
    hostname: string
    timestamp: number
    wxid: string // 可能为空字符串
    type: number // 外部 type
    data: string // data 内容需要二次解析
  },
  Ext
> // 部分消息会有额外字段

// 解析后的消息
export type RecvMessage<Data, Ext extends AnyObject = never> = Omit<
  RawRecvMessage<Ext>,
  'data' | 'wxid'
> & {
  accountId: string
  wxid: string | null // wxid 为空字符串时会被转换成 null
  data: Data
}

// hook 消息通常的 RecvMessage Data 格式
export interface HookData<T = unknown> {
  type: number
  data: T
}

/* 发送消息 */

// 管理类消息，例如创建容器（仅极少数情况会发送此类消息）
type SendManageMessage<Data extends AnyObject, Ext extends AnyObject = never> = SafeCombine<
  {
    msgId: string
    type: number
    wxid: ''
    data: Data
  },
  Ext
> // 部分管理类消息会有额外字段

// hook 消息
interface SendHookMessage<Data extends AnyObject> {
  msgId: string
  wxid: string
  type: number // 和 data 里的 type 相同，这个是用来后端判断用的。TODO: 后面看能不能让后端优化下，直接读里面的 type，就不用这一个字段了
  data: {
    type: number
    data: Data
  }
}

export type SendMessage<Data extends AnyObject, Ext extends AnyObject = never> =
  | SendManageMessage<Data, Ext>
  | SendHookMessage<Data>

/**
 * ==============================
 * 数据解析、封装
 * ==============================
 */
export class ProtocolError extends Error {
  error?: Error
  constructor(message: string, originError?: Error) {
    super(message)
    this.error = originError
  }
}

/**
 * 解析接收到的消息
 */
export function parse<Data, Ext extends AnyObject = never>(payload: string, accountId: string) {
  let rawMessage: RawRecvMessage<Ext>
  try {
    rawMessage = JSON.parse(payload) as RawRecvMessage<Ext>
  } catch (error) {
    throw new ProtocolError('payload 解析失败', error as Error)
  }
  const wxid = rawMessage.wxid || null
  const messageData = parseRecvMessageData<Data>(rawMessage.data)
  return { ...rawMessage, wxid, data: messageData, accountId } as RecvMessage<Data, Ext>
}

export function parseRecvMessageData<Data>(raw: unknown) {
  if (typeof raw === 'string') {
    try {
      return JSON.parse(raw || '{}') as Data
    } catch (error) {
      throw new ProtocolError('message data 解析失败', error as Error)
    }
  } else {
    return raw as Data
  }
}

/**
 * 判断接收到的消息的类型
 */
export function getRecvMessageType<Msg extends RecvMessage<unknown>>(message: Msg) {
  const { type } = message
  if (type === 30002) {
    return message.wxid ?? '' ? MessageType.Hook : MessageType.HookPlain
  } else {
    return MessageType.Manage
  }
}

/**
 * 封装要发送的 manage 消息
 */
function makeManageMessage<Data extends AnyObject>(
  type: number,
  data: Data,
): SendManageMessage<Data>
function makeManageMessage<Data extends AnyObject, Ext extends AnyObject>(
  type: number,
  data: Data,
  ext: Ext,
): SendManageMessage<Data, Ext>
function makeManageMessage<Data extends AnyObject, Ext extends AnyObject>(
  type: number,
  data: Data,
  ext?: Ext,
): SendManageMessage<Data> | SendManageMessage<Data, Ext> {
  return { msgId: uuidv4(), type, wxid: '', data, ...data, ...(ext ?? {}) }
}
export { makeManageMessage }

/**
 * 封装要发送的 hook 消息
 */
export function makeHookMessage<Data extends AnyObject>(
  wxid: string,
  type: number,
  data: Data,
): SendHookMessage<Data> {
  return { msgId: uuidv4(), wxid, type, data: { type, data } }
}
